import cplex
import random
import numpy as np
from cplex.exceptions import CplexError

#The ideal case is that
#   createld(prob, rownames)
#   solveld (prob, lambda, A, b)
def getidx(i,j,N):
    return j*N + i + 1

def initip(seed=10, N=20, b=100):
    random.seed(seed)

    prob = cplex.Cplex()

    prob.objective.set_sense(prob.objective.sense.maximize)
    
    # Add variables
    varnames = ["x" + str(getidx(i,j,N)) for j in range(N) for i in range(N)]
    #orgobj  =  [random.uniform(1,10)  for i in range(N) for j in range(N)]
    orgobj  =  [random.randint(1,10)  for i in range(N) for j in range(N)]
    vartypes = ["B" for i in range(N) for j in range(N)]
    prob.variables.add(obj = orgobj,
                       types = vartypes,
                       names = varnames)

    # Add constant in the objective
    prob.variables.add(obj = [0],
                       types = ["C"],
                       lb = [1],
                       ub = [1],
                       names = ["x0"])
                       
    # Populate U constraints
    unames  = ["u" +str(i+1) for i in range(N) ]
    urowcoefs = [1] * N
    usenses = ["E"] * N
    urhs    = [1] * N
    
    uexpr  = []
    for i in range(N):
        urowvars = ["x" + str(getidx(i,j,N)) for j in range(N)]
        uexpr.append([urowvars, urowcoefs])
        
    prob.linear_constraints.add(lin_expr = uexpr,
                                    senses = usenses,
                                    rhs = urhs,
                                    names = unames)
        
    # Populate V constraints
    vnames  = ["v" + str(j+1) for j in range(N) ]
    vrowcoefs = [1] * N
    vsenses = ["E"] * N
    vrhs    = [1] * N
        
    vexpr  = []
    for j in range(N):
        vrowvals = ["x" + str(getidx(i,j,N)) for i in range(N)]
        vexpr.append([vrowvals, vrowcoefs])
            
    prob.linear_constraints.add(lin_expr = vexpr,
                                senses = vsenses,
                                rhs = vrhs,
                                names = vnames)
            
    # Populate knapsack constraints 
    knames =  ["k"]
    krownames = varnames
    #This is t_ij
    t_lo = int(0.5 * b/ (1*N) )
    t_up = int(3.0 * b/ (1*N) )
    #krowcoefs = [random.uniform(t_lo, t_up) for i in range(N*N) ]
    krowcoefs = [random.randint(t_lo, t_up) for i in range(N*N)]
    kcoefs = [krownames, krowcoefs]
    prob.linear_constraints.add(lin_expr = [kcoefs],
                                senses = ["L"],
                                rhs = [b],
                                names = ["k"])

    #prob.parameters.mip.display.set(0)    
    prob.set_results_stream(None)
    return prob
#prob.write("lagrange.lp")
                          
#def solvelr(prob, lamba, A, b):
#    prob.objective.set_linear(varnames, value)

def initlr(prob, dual_rows):
    #print dual_rows
    A = prob.linear_constraints.get_rows(dual_rows)
    b = prob.linear_constraints.get_rhs(dual_rows)
    sen = prob.linear_constraints.get_senses(dual_rows)
    prob.linear_constraints.delete(dual_rows)
    c = prob.objective.get_linear()
    return A, b, sen, c

def solvelr(prob, l, A, b, c):
    prob.parameters.mip.tolerances.mipgap.set(0)
    #update constants in 
    #set old coefficients
    varnames = prob.variables.get_names()
    prob.objective.set_linear( zip(varnames, c) )

    const = np.dot(l, b)
    prob.objective.set_linear("x0", const)        

    #This is a confusing part, we modify the coefficients with l
    for i in range( len(l) ):
        temp_ind = A[i].ind
        temp_val = A[i].val
        old_coef = prob.objective.get_linear(temp_ind)
        new_coef = np.add(old_coef, -np.multiply(l[i], temp_val) )
        prob.objective.set_linear( zip(A[i].ind, new_coef) )

    prob.solve()
    #        for j in range( A[i].ind ):
    #            old_coef = prob.objective.get_linear(j)
    #            new_coef = old_coef * lambda[i] * 
    #            prob.objective.set_linear( j, new_coef )

    return 0

def gap(x, A, b):
    #compute b - Ax
    gap = np.zeros( len(b) )

    for i in range( len(b) ):
        temp_ind = A[i].ind
        temp_val = A[i].val
        gap[i] = b[i] - np.dot( x[temp_ind], temp_val )
#        print x[temp_ind], temp_val
#        print b[i]
    return gap


def opt_test(x, l, A, b, sen):
    g = gap(x, A, b)
    #print "g", g, "l", l
    #print abs(np.dot(l, g))
    if abs(np.dot(l, g)) != 0.0:
        #print "False at complementary"
        return False

    for i in range(len(l)):
        if   sen[i] == 'E' and g[i] != 0:             #d - Dx != 0
        #    print "False at equality"
            return False
        elif sen[i] == 'L' and g[i] < 0:            #d - Dx < 0
        #    print "False at inequality L"
            return False
        elif sen[i] == 'G' and g[i] > 0:
        #    print "False at inequality G"
            return False
        else:
            continue
        
    return True

